package cn.shafish.beans.factory.support;

import cn.shafish.beans.BeanDefinition;
import cn.shafish.beans.factory.BeanCreationException;
import cn.shafish.beans.factory.BeanFactory;
import cn.shafish.util.ClassUtils;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 从beanFactory接口继承的类，改类主要实现功能：
 *      I. 读取bean配置文件中bean的属性，并保存各属性值到map中 -- 构造方法
 *      II. 提供获取bean定义信息的调用 -- getBeanDefinition("beanID")
 *      III. 提供获取bean实例的调用 -- getBean("beanID")
 */
public class DefaultBeanFactory implements BeanFactory {
    // 使用常量保存需要从配置文件中获取的属性，这里只需获取id和class对应的值
    public static final String ID_ATTRIBUTE = "id";
    public static final String CLASS_ATTRIBUTE = "class";
    // 保存bean 配置文件中的bean信息id+class
    private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, BeanDefinition>();

    // 构造函数加载xml配置文件信息
    public DefaultBeanFactory(String configFile) {
        loadBeanDefinition(configFile);
    }

    private void loadBeanDefinition(String configFile) {
        InputStream is = null;
        try{
            //1. 读取配置文件信息到输入流is中
            ClassLoader cl = ClassUtils.getDefaultClassLoader();
            is = cl.getResourceAsStream(configFile);
            //2. 使用dom4j开始分析流信息
            SAXReader reader = new SAXReader();
            Document doc = reader.read(is);
            //2.1
            Element root = doc.getRootElement();
            Iterator<Element> iterator = root.elementIterator();
            while (iterator.hasNext()) {
                Element ele = (Element) iterator.next();
                String id = ele.attributeValue(ID_ATTRIBUTE);
                String beanClassName = ele.attributeValue(CLASS_ATTRIBUTE);
                // BeanDefinition是接口，GenericBeanDefinition为其实现
                BeanDefinition bd = new GenericBeanDefinition(id,beanClassName);
                this.beanDefinitionMap.put(id,bd);
            }
        } catch (DocumentException e) {
            e.printStackTrace();
        } finally {
            if(is != null) {
                try{
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public BeanDefinition getBeanDefinition(String beanId) {
        return this.beanDefinitionMap.get(beanId);
    }

    public Object getBean(String beanId) {
        BeanDefinition beanDefinition = this.beanDefinitionMap.get(beanId);
        if(beanDefinition == null) {
            throw new BeanCreationException("Bean Definition not exist");
        }
        ClassLoader cl = ClassUtils.getDefaultClassLoader();
        String beanClassName = beanDefinition.getBeanClassName();
        try{
            // 获取到className方法名后就可以通过放射的方法把实例创建出来
            Class<?> clz = cl.loadClass(beanClassName);
            return clz.getDeclaredConstructor().newInstance(); // 调用无参的构造方法创建实例
        } catch (Exception e) {
            throw new BeanCreationException("create bean "+beanClassName+" failed",e);
        }
    }
}
